﻿namespace dogd
{
	using System;
	using System.Data;
	using System.Reflection;

	/// <summary>
	/// Metadata modelling one column in a database table.
	/// </summary>
	public struct ColumnMetadata : IEquatable<ColumnMetadata>
	{
		private string columnName;
		
		private DbType storageType;
		
		private string propertyName;
	
		private Type propertyType;

		private ColumnOption option;

		/// <summary>
		/// Initialize from explicit data.
		/// </summary>
		/// <param name="columnName"></param>
		/// <param name="dataType"></param>
		/// <param name="propertyName"></param>
		/// <param name="propertyType"></param>
		/// <param name="option"></param>
		public ColumnMetadata(string columnName, DbType dataType, string propertyName, Type propertyType, ColumnOption option)
		{
			this.columnName = columnName;
			this.storageType = dataType;
			this.propertyName = propertyName;
			this.propertyType = propertyType;
			this.option = option;
		}

		/// <summary>
		/// Initialize using ColumnAttribute and PropertyInfo from reflection.
		/// </summary>
		/// <param name="attribute"></param>
		/// <param name="info"></param>
		public ColumnMetadata(ColumnAttribute attribute, PropertyInfo info)
		{
			this.columnName = attribute.ColumnName;
			this.storageType = attribute.StorageType;
			this.propertyName = info.Name;
			this.propertyType = info.PropertyType;

			if (attribute.IsLookup)
			{
				this.option = ColumnOption.Lookup;
			}
			else if (attribute.IsPrimaryKey)
			{
				this.option = ColumnOption.PrimaryKey;
			}
			else if (attribute.IsForeignKey)
			{
				this.option = ColumnOption.ForeignKey;
			}
			else
			{
				this.option = ColumnOption.None;
			}
		}

		/// <summary>
		/// The name of the column in the database.
		/// </summary>
		public string ColumnName
		{
			get { return this.columnName; }
		}

		/// <summary>
		/// The type of the data in the database.
		/// </summary>
		public DbType StorageType
		{
			get { return this.storageType; }
		}

		/// <summary>
		/// The name of the property in the class.
		/// </summary>
		public string PropertyName
		{
			get { return this.propertyName; }
		}

		/// <summary>
		/// The type of the class property.
		/// </summary>
		public Type PropertyType
		{
			get { return this.propertyType; }
		}

		/// <summary>
		/// Whether this column is used for lookups.
		/// </summary>
		public bool IsLookup
		{
			get { return this.option == ColumnOption.Lookup; }
		}

		/// <summary>
		/// Whether this column represents a primary key.
		/// </summary>
		public bool IsPrimaryKey
		{
			get { return this.option == ColumnOption.PrimaryKey; }
		}

		/// <summary>
		/// Whether this column represents a foreign key.
		/// </summary>
		public bool IsForeignKey
		{
			get { return this.option == ColumnOption.ForeignKey; }
		}

		/// <summary>
		/// A string representing the variable name used in parameterised queries.
		/// </summary>
		public string Parameter
		{
			get { return "@" + this.PropertyName; }
		}

		/// <summary>
		/// A string representing the column name, variable name fragment.
		/// </summary>
		public string FormattedParameterString
		{
			get
			{
				return string.Format("{0} = @{1}", this.ColumnName, this.PropertyName);
			}
		}

		/// <summary>
		/// Implementation for IEquatable.
		/// </summary>
		/// <param name="other"></param>
		/// <returns></returns>
		public bool Equals(ColumnMetadata other)
		{
			if (this.columnName != other.columnName)
			{
				return false;
			}

			return this.storageType == other.storageType; 
		}

		/// <summary>
		/// Implementation for IEquatable.
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return this.columnName.GetHashCode();
		}

		/// <summary>
		/// Implementation for IEquatable.
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}

			if (GetType() != obj.GetType())
			{
				return false;
			}

			ColumnMetadata column2 = (ColumnMetadata)obj;

			if (this.columnName != column2.columnName)
			{
				return false;
			}

			return this.storageType == column2.storageType;
		}

		/// <summary>
		/// Compares one ColumnMetadata object with another for equality.
		/// </summary>
		/// <param name="column1"></param>
		/// <param name="column2"></param>
		/// <returns></returns>
		public static bool operator ==(ColumnMetadata column1, ColumnMetadata column2)
		{
			return column1.Equals(column2);
		}

		/// <summary>
		/// Tests one ColumnMetadata object against another for non-equality.
		/// </summary>
		/// <param name="column1"></param>
		/// <param name="column2"></param>
		/// <returns></returns>
		public static bool operator !=(ColumnMetadata column1, ColumnMetadata column2)
		{
			return !column1.Equals(column2);
		}    
	}
}
